<!--
  Uploader 文件上传组件                 zls-2023/07/31
    所有属性、事件、方法、插槽，请参考  https://vant-contrib.gitee.io/vant/#/zh-CN/uploader

    Attributes
      1、modelValue       文件列表，v-model
      2、fileTypeList     需要上传的文件类型 ['image', 'word', 'excel', 'ppt', 'txt', 'pdf', 'zip', 'audio', 'video']，默认为['image']

    Demo
      <z-uploader v-model="form.fileList1" label="文件上传" :fileTypeList="['image']" :max-count="3"></z-uploader>
      <z-uploader v-model="form.fileList2" label="文件上传默认值" :fileTypeList="['image', 'zip']"></z-uploader>
      
      const form = reactive({
        fileList1: null,
        fileList2: [{ url: 'https://fastly.jsdelivr.net/npm/@vant/assets/leaf.jpeg' }, { url: 'https://fastly.jsdelivr.net/npm/@vant/assets@1.0.8/design/axure-20200905.zip' }]   // 格式必须是[ { url: '' }, { url: '' } ] 
      })
-->
<template>
  <z-input
    :label="$attrs['label']"
    :required="$attrs['required']"
    :border="$attrs['border']"
    :label-width="$attrs['label-width']"
    :label-align="$attrs['label-align'] || 'top'"
    :rules="$attrs['required'] || $attrs['required'] === '' ? [{ required: true, message: '请选择' }] : []"
  >
    <template #input>
      <Uploader
        v-model="fileList.list"
        v-bind="{ ...$attrs, 'preview-size': $attrs['preview-size'] || pxToRem(77), accept: acceptStr, 'before-read': beforeRead, 'after-read': afterRead, 'before-delete': deleteFile }"
        @click-preview="clickPreview"
      >
        <template #preview-cover="{ type, file: { name } }">
          <img v-if="type !== 'image'" class="preview-cover" :src="fileTypeObj[type]" alt="" />
          <div class="preview-tip">{{ name }}</div>
        </template>
      </Uploader>
    </template>
  </z-input>
</template>

<script>
export default { name: 'z-uploader' }
</script>
<script setup>
import { reactive, computed } from 'vue'
import { Uploader, showFailToast, showNotify, showConfirmDialog } from 'vant'
import { checkFile, pxToRem } from '@/utils'
import word from '@img/uploader/word.png'
import excel from '@img/uploader/excel.png'
import ppt from '@img/uploader/ppt.png'
import txt from '@img/uploader/txt.png'
import pdf from '@img/uploader/pdf.png'
import zip from '@img/uploader/zip.png'
import audio from '@img/uploader/audio.png'
import video from '@img/uploader/video.png'
const emit = defineEmits(['update:modelValue'])

const props = defineProps({
  modelValue: {
    type: Array,
    default: () => null
  },
  // 需要上传的文件类型 ['image', 'word', 'excel', 'ppt', 'txt', 'pdf', 'zip', 'audio', 'video']
  fileTypeList: {
    type: Array,
    default: () => ['image']
  }
})

// 根据文件类型，设置对应的图片
const fileTypeObj = { word, excel, ppt, txt, pdf, zip, audio, video }
// 文件类型对应的文件格式
const acceptObj = reactive({
  image: 'image/*',
  word: 'application/msword',
  excel: 'application/vnd.ms-excel',
  ppt: 'application/vnd.ms-powerpoint',
  txt: 'text/plain',
  pdf: 'application/pdf Portable',
  zip: 'aplication/zip',
  audio: 'audio/*',
  video: 'video/*'
})
// 文件类型对应的提示文字
const tipObj = reactive({
  image: '图片',
  word: 'word',
  excel: 'excel',
  ppt: 'ppt',
  txt: 'txt文本',
  pdf: 'pdf',
  zip: 'zip压缩包',
  audio: '音频',
  video: '视频'
})
// 文件类型对应的文件大小限制
const fileSizeObj = reactive({
  image: 1024 * 1024 * 2,
  word: 1024 * 1024 * 20,
  excel: 1024 * 1024 * 20,
  ppt: 1024 * 1024 * 20,
  txt: 1024 * 1024 * 20,
  pdf: 1024 * 1024 * 20,
  zip: 1024 * 1024 * 500,
  audio: 1024 * 1024 * 500,
  video: 1024 * 1024 * 500
})

const fileList = reactive({ list: [] }) // 文件列表，格式：[{url:'',type:'',name:''}]
// 初始化默认值
if (props.modelValue) {
  props.modelValue.forEach(async item => {
    const fileObj = await urlToFile(item.url)
    fileList.list.push(fileObj)
  })
} else {
  fileList.list = []
}

// 过滤得到允许上传的文件格式
const acceptStr = computed(() => {
  let str = ''
  props.fileTypeList.forEach(type => {
    str += `${acceptObj[type]},`
  })
  str = str.slice(0, -1)
  return str
})
// 过滤得到不允许上传的提示文字
const tipStr = computed(() => {
  let str = ''
  props.fileTypeList.forEach(type => {
    str += `${tipObj[type]}、`
  })
  str = str.slice(0, -1)
  return str
})

// 将网络地址转为file对象
function urlToFile(url) {
  return new Promise((resolve, reject) => {
    const fileName = url.match(/[^/]+$/)[0] // 获取url的文件名称
    const exe = url.match(/[^.]+$/)[0] // 获取url的文件后缀名
    const type = checkFile(exe) // 获取url的文件类型
    const fileObj = {
      url,
      type,
      exe,
      file: null
    }
    let blob = null
    let file = null
    let xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.setRequestHeader('Accept', acceptObj[type])
    xhr.responseType = 'blob'
    xhr.onload = () => {
      blob = xhr.response
      fileObj.file = new File([blob], fileName, { type: acceptObj[type] })
      resolve(fileObj)
    }
    xhr.onerror = e => {
      reject(e)
    }
    xhr.send()
  })
}

// 文件读取前的回调函数(常用于限制文件的上传类型及文件大小等)
function beforeRead(file) {
  console.log('---上传前的文件---', file)
  const type = checkFile(file.name) // 文件类型
  if (file.size > fileSizeObj[type]) {
    const size = fileSizeObj[type] / 1024 / 1024
    showFailToast(`${tipStr.value}的文件大小不能超过${size}MB`)
    return false
  }
  if (!props.fileTypeList.includes(checkFile(file.name))) {
    showFailToast(`请上传${tipStr.value}`)
    return false
  }
  return true
}

// 上传成功(调用后台文件服务接口，将文件上传至文件服务，更新文件列表)
function afterRead(file) {
  fileList.list.forEach(v => {
    v.type = checkFile(v.file.name) // 文件类型
    v.exe = v.file.name.match(/[^.]+$/)[0] // 文件后缀名
  })
  emit('update:modelValue', fileList.list)
  // file.status = 'uploading'
  // file.message = '上传中...'
  // setTimeout(() => {
  //   const type = checkFile(file.file.name) // 文件类型
  //   const exe = file.file.name.match(/[^.]+$/)[0] // 文件后缀名
  //   fileList.list.push({ url: 'https://img0.baidu.com/it/u=3976458258,1573032133&fm=253&fmt=auto&app=138&f=PNG?w=977&h=370', type, exe })
  //   file.status = 'done'
  //   file.message = '上传成功'
  //   emit('update:modelValue', fileList.list)
  //   showNotify({ type: 'success', message: '上传成功' })
  // }, 1000)
}

// 删除文件时触发（调用后台文件服务删除该文件）
function deleteFile(file, detail) {
  fileList.list.splice(detail.index, 1)
  emit('update:modelValue', fileList.list.length ? fileList.list : null)
  // showConfirmDialog({ message: '确定删除该文件吗?' })
  //   .then(() => {
  //       fileList.list.splice(detail.index, 1)
  //       emit('update:modelValue', fileList.list)
  //       showNotify({ type: 'success', message: '删除成功' })
  //   })
  //   .catch(() => {})
}

// 点击文件直接打开（除image图片外）
function clickPreview(file) {
  console.log('--打开文件--', file)
  if (checkFile(file.file.name) === 'image') return
  window.open(file.url)
}
</script>

<style lang="scss" scoped>
:deep(.van-uploader__upload) {
  .van-icon {
    font-size: 30px;
  }
}
:deep(.van-image) {
  display: block;
}
.preview-cover {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.preview-tip {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 24px;
  line-height: 24px;
  padding: 0 2px;
  color: #fff;
  font-size: 12px;
  text-align: center;
  background: rgba(0, 0, 0, 0.4);
  @include oflow();
}
</style>
